#pragma once

#include "MBDataTypes.h"
#include "MBEnums.h"
#include "MBCameraView.h"
#include "MBDistanceMap.h"
#include "MBLabelingOptions.h"

#include <set>
#include <vector>
#include <list>

//class MBOctree;

/**
  *	An MBCameraPosIDWithVertexIndices object capsules the ID of a camera position
  * and two vertex indices. These three points in space define a triangle. In
  * the labeling process an MBCameraPosIDWithVertexIndices object is stored for
  * every vertex-vertex-camPos triangle that intersects with at least one other
  * triangle of the model.
  */
class MBCameraPosIDWithVertexIndices
{
public:
	/**
	  *	Constructs a new MBCameraPosIDWithVertexIndices object with the given
	  * indices.
	  *
	  * @param cameraPosID ID of a camera position
	  * @param firstVertexIndex index of the first vertex
	  * @param secondVertexIndex index of the second vertex
	  */
	MBCameraPosIDWithVertexIndices(
		const MBuint &cameraPosID,
		const MBuint &firstVertexIndex,
		const MBuint &secondVertexIndex ) :
	mCameraPosID( cameraPosID ),
	mFirstVertexIndex( firstVertexIndex ),
	mSecondVertexIndex( secondVertexIndex ) {}

	/**
	  *	Compares this MBCameraPosIDWithVertexIndices object with another one.
	  * Returns true if this object is smaller than the other one, false
	  * otherwise. At first the camera position IDs are compared, then the
	  * (sorted) vertex indices are compared.
	  *
	  * @param other MBCameraPosIDWithVertexIndices object that is compared with this one
	  * @return true if this object is smaller than the other one, false otherwise
	  */
	bool operator<( const MBCameraPosIDWithVertexIndices &other ) const
	{
		if (mCameraPosID < other.mCameraPosID)
		{
			return true;
		}
		else if (mCameraPosID > other.mCameraPosID)
		{
			return false;
		}
		else
		{
			MBuint vertex_indices[2] = { mFirstVertexIndex, mSecondVertexIndex };
			MBuint other_vertex_indices[2] = { other.mFirstVertexIndex, other.mSecondVertexIndex };

			MBuint helper;
			
			// Sort
			if (vertex_indices[0] > vertex_indices[1])
			{
				helper = vertex_indices[0];
				vertex_indices[0] = vertex_indices[1];
				vertex_indices[1] = helper;
			}

			if (other_vertex_indices[0] > other_vertex_indices[1])
			{
				helper = other_vertex_indices[0];
				other_vertex_indices[0] = other_vertex_indices[1];
				other_vertex_indices[1] = helper;
			}

			// Compare

			if (vertex_indices[0] < other_vertex_indices[0])
			{
				return true;
			}
			else if (vertex_indices[0] > other_vertex_indices[0])
			{
				return false;
			}
			else
			{
				if (vertex_indices[1] < other_vertex_indices[1])
				{
					return true;
				}

				return false;
			}
		}
	}
private:
	/** ID of a camera position */
	MBuint mCameraPosID;
	/** Index of the first vertex */
	MBuint mFirstVertexIndex;
	/** Index of the second vertex */
	MBuint mSecondVertexIndex;
};

/**
  *	An MBTriangle is a triangle in 3D space defined by three vertexes.
  */
class MBTriangle
{
	friend class MBEnergyMinimizer;
public:
	MBTriangle( const MBuint &firstVertexIndex,
		const MBuint &secondVertexIndex,
		const MBuint &thirdVertexIndex,
		const std::vector<double*> *vertexCoordinates );

// 	MBTriangle( const MBuint &firstVertexIndex,
// 				const MBuint &secondVertexIndex,
// 				const MBuint &thirdVertexIndex,
// 				const std::vector<double*> *vertexCoordinates,
// 				const MBOctree *octree );

	void AddNeighbor( MBTriangle *neighbor );
// 	void AddIntersectingSightTriangleIndicesToSet(
// 		const MBCameraView *label,
// 		const MBLabelingOptions &options ) const;
	void CalculateNormal( std::vector<double*> &vertexCoordinates );
	const std::set<MBTriangle*> &GetNeighbors( void ) const;
	const double *GetNormal( void ) const;
	const MBuint *GetVertexIndices( void ) const;
	const double *GetVertexPosition( const MBuint &index ) const;
	
	void GetTextureCoords( const MBCameraView *label, std::vector<MBTextureCoordinate> *textureCoordinate ) const;
	double GetSquaredSineViewNormal(
		const MBCameraView *label,
		const MBLabelingOptions &labelingOptions,
		const MBuint *firstVertexIndex = 0,
		const MBuint *secondVertexIndex = 0 ) const;
	double GetWeightD(
		const MBCameraView *label,
		MBDistanceMap *distanceMap,
		const MBLabelingOptions &labelingOptions ) const;
	double GetWeightDIntegral( const MBCameraView *label, MBDistanceMap *distanceMap, const MBuint &imagePyramidLevel = 0u ) const;
	double GetWeightV(
		const MBCameraView *label,
		const MBTriangle *neighborNode,
		const MBCameraView *neighborLabel,
		MBDistanceMap *distanceMap,
		const MBLabelingOptions &labelingOptions,
		const MBFilteringMode &filteringMode = MB_FILTERMODE_NEAREST_NEIGHBOR,
		const MBuint &imagePyramidLevel = 0,
		const MBint &numSamples = -1 ) const;

	double GetRealAreaPerProjectedArea(const MBCameraView &cameraView) const;

	bool IsInfinitelyThin( void ) const;
	bool IsTriangleCompletelyInside( const MBCameraView &cameraView, MBDistanceMap *distanceMap ) const;

	/** Value corresponding to a label that is absolutely not suitable to label a particular triangle */
	static double WEIGHT_D_INFINITY;
	/** Value corresponding to the case, that two labels are absolutely not suitable to be used to label two neighbored triangles */
	static double WEIGHT_V_INFINITY;

private:
	/** The three vertexes the MBtriangle object consists of */
	MBuint mVertexIndices[3];
	/** Vector with pointers to all MBTriangles that are adjacent to this one */
	std::set<MBTriangle*> mNeighbors;
	/** 3D normal vector */
	double mNormal[3];
	/** Boolean value indicating whether this triangle is infinitely thin */
	bool mInfinitelyThin;

	/** Const pointer to a vector with all the 3D vertex coordinates of the model */
	const std::vector<double*> *mVertexCoordinates;

	/** Const pointer to the Octree that contains all the triangles of the model */
	// const MBOctree *mOctree;

	/** Set of all vertex-vertex-camPos triangles that intersect with at least one triangle of the model */
	static std::set<MBCameraPosIDWithVertexIndices> indicesInIntersectingSightTriangles;

	/** Generate all points between two given points by linear interpolation */
	void linearInterpolation(std::vector<MBVec2> *points) const;

	/** Get the projected points of the triangle */
	void GetProjectedPoints(std::vector<MBVec2> &image_points, const MBCameraView &cameraView) const;

	/** Check if the projected points of the triangle lie inside the image mask */
	bool IsTriangleInsideMask(const MBCameraView &cameraView) const;

	/** get all points of the projected triangle boundary */
	void GetProjectedTriangleBoundary(std::list<MBVec2> &triangleBoundary, const MBCameraView &cameraView) const;

	/** check if all points are inside the mask */
	static bool PointsInsideMask(const std::vector<MBVec2> &points, const MBCameraView &cameraView);

	/** check if the projected edge of the triangle, given by two of the triangle's vertex indices, lies inside the image mask */
	bool  IsEdgeInsideMask(const MBuint &indexVertex1, const MBuint &indexVertex2, const MBCameraView &cameraView) const;

	static double ms_AccumTimeForOcclusionDetectionSeconds;
};